import { isFunction } from "./utils";

export interface SketionRequestOptions {
  rejectError?: boolean;
}

export interface SketionConfig<Q extends SketionRequestOptions, R> {
  getDataFromCache?: (request: Q) => Promise<R> | null;
  sendRequest: (request: Q) => Promise<any>;
  beforeRequest: (request?: Q) => void;
  afterRequest: (response: R, request?: Q) => R | Promise<R>;
  parseRes: (response: any) => R | Promise<R>;
  parseError: (error: any) => R;
}

export type RequestTaskType = Promise<any> | (() => Promise<any>);

export function createSketion<Q extends SketionRequestOptions, R>(
  config: SketionConfig<Q, R>
) {
  return new SketionHttp<Q, R>(config);
}

export class SketionHttp<Q extends SketionRequestOptions, R> {
  public config: SketionConfig<Q, R>;

  constructor(config: SketionConfig<Q, R>) {
    this.config = config;
  }

  public sendRequest = async (requestOptions: Q): Promise<R> => {
    return this.embraceRequest(
      () => this.config.sendRequest(requestOptions),
      requestOptions
    );
  };

  public embraceRequest = async (
    request: RequestTaskType,
    opts?: Q
  ): Promise<R> => {
    const { rejectError = false } = opts || {};

    try {
      let res;
      if (isFunction(request)) {
        // 先去缓存中获取数据
        if (this.config.getDataFromCache && opts) {
          const cache = this.config.getDataFromCache(opts);
          if (cache !== undefined && cache !== null) {
            return Promise.resolve(cache);
          }
        }
        this.config.beforeRequest(opts);
        res = await (request as () => Promise<any>)();
      } else {
        this.config.beforeRequest(opts);
        res = await request;
      }

      const myResponse = await Promise.resolve(this.config.parseRes(res));

      const afterRes = await Promise.resolve(
        this.config.afterRequest(myResponse, opts)
      );
      return Promise.resolve(afterRes);
    } catch (e) {
      const errorRes = await Promise.resolve(this.config.parseError(e));

      const afterRes = await Promise.resolve(
        this.config.afterRequest(errorRes, opts)
      );
      if (rejectError) {
        return Promise.reject(afterRes);
      } else {
        return Promise.resolve(afterRes);
      }
    }
  };
}
